package org.springblade.modules.mes.opcua.server;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springblade.modules.mes.base.constant.ScanTypeEnum;
import org.springblade.modules.mes.base.entity.*;
import org.springblade.modules.mes.base.service.ILmsProductionOrderService;
import org.springblade.modules.mes.base.service.IProducePlanService;
import org.springblade.modules.mes.base.service.IStationAssemblyService;
import org.springblade.modules.mes.base.service.IStationBatchService;
import org.springblade.modules.mes.opcua.constant.CountMapUtil;
import org.springblade.modules.mes.opcua.constant.OpcUaCache;
import org.springblade.modules.mes.opcua.constant.OpcUaError;
import org.springblade.modules.mes.opcua.constant.OpcUaKey;
import org.springblade.modules.mes.opcua.itf.MesMode;
import org.springblade.modules.mes.opcua.itf.OpcUa;
import org.springblade.modules.mes.opcua.vo.OpcUaVO;
import org.springblade.modules.mes.opcua.vo.ScanTemp;
import org.springblade.modules.mes.quality.entity.AnalysisMaterialCompositionMysql;
import org.springblade.modules.mes.quality.service.IAnalysisMaterialCompositionMysqlService;
import org.springblade.modules.mes.quality.vo.MaterialCountVO;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 产品生产相关
 */
@Service
@AllArgsConstructor
@Slf4j
public class ToolService {


	private final IStationAssemblyService stationAssemblyService;

	private final IStationBatchService stationBatchService;

	private final ILmsProductionOrderService lmsProductionOrderService;

	private final IAnalysisMaterialCompositionMysqlService analysisMaterialCompositionService;

	private final IProducePlanService planService;

	//PLC**************************************************************************************

	/**
	 * 发送消息
	 */
	public void plcMessage(OpcUaVO ctx, OpcUaError error) throws Exception {
		if (MesMode.mockFlag()) {
			OpcUa.getInstance().writeValue(ctx, null, OpcUaKey.message, error.getError());
		} else {
			OpcUa.getInstance().writeValue(ctx, null, OpcUaKey.message, error.name());
		}

	}


	/**
	 * 产品上架
	 */
	public void plcAssemblyOnScan(OpcUaVO ctx, String position, LMSDetail order) throws Exception {
		//扫描物料后返回
		PrdInfo prdInfo = OpcUaCache.getByProdCode(order.getMatCode());
		OpcUa.getInstance().writeValue(ctx, position, OpcUaKey.prodBarCode, order.getBarCode());
		OpcUa.getInstance().writeValue(ctx, position, OpcUaKey.assemblyNo, order.getMatCode());

		short mt = 0;
		if (prdInfo.getMaterialType() != null) {
			try {
				mt = Short.valueOf(prdInfo.getMaterialType());
			} catch (Exception ex) {
			}
		}
		OpcUa.getInstance().writeValue(ctx, null, OpcUaKey.prodType, mt);


		short mst = 0;
		if (prdInfo.getMaterialType() != null) {
			try {
				mst = Short.valueOf(prdInfo.getMaterialSubType());
			} catch (Exception ex) {
			}
		}
		OpcUa.getInstance().writeValue(ctx, null, OpcUaKey.prodSubType, mst);
		OpcUa.getInstance().writeValueShortFinal(ctx, null, OpcUaKey.scanStatus, (short) 2);
	}


	/**
	 * 更新扫描状态
	 *
	 * @param ctx
	 */
	public void plcScanStatus(OpcUaVO ctx, Object v) throws Exception {
		OpcUa.getInstance().writeValueShortFinal(ctx, null, OpcUaKey.scanStatus, v);
	}


	/**
	 * 读取扫码值
	 *
	 * @param ctx
	 */
	public Object plcScanCode(OpcUaVO ctx) throws Exception {
		Object scanCode = OpcUa.getInstance().readValue(ctx.opcuaKey(OpcUaKey.scanCode, null));
		return scanCode;
	}


	/**
	 * 读取扫码值
	 *
	 * @param ctx
	 */
	public Object plcPrdCode(OpcUaVO ctx) throws Exception {
		Object scanCode = OpcUa.getInstance().readValue(ctx.opcuaKey(OpcUaKey.prodBarCode, null));
		return scanCode;
	}

	//PLC**************************************************************************************

	/**
	 * 查询订单
	 *
	 * @param barCode
	 * @return
	 */
	public LMSDetail findOrder(String barCode) {
		//查询订单
		LMSDetail oldOrder = lmsProductionOrderService.getOne(new QueryWrapper<LMSDetail>().lambda()
			.eq(LMSDetail::getBarCode, barCode)
			.last("limit 1"));
		return oldOrder;
	}

	/**
	 * 更新订单状态
	 *
	 * @param barCode
	 * @return
	 */
	public void updateOrderStatus(String barCode, Integer status, String newBarCode) {
		UpdateWrapper<LMSDetail> wrapper = new UpdateWrapper<>();
		wrapper.eq("bar_code", barCode);
		wrapper.set("status", status);
		if (newBarCode != null && newBarCode.length() > 0) {
			wrapper.set("bar_code", newBarCode);
		}
		lmsProductionOrderService.update(wrapper);
	}

	/**
	 * 更新订单状态
	 *
	 * @return
	 */
	public void updatePlanStatus(String planCode) {
		Long endCount = lmsProductionOrderService.count(new QueryWrapper<LMSDetail>()
			.lambda()
			.in(LMSDetail::getPlanCode, planCode)
			.eq(LMSDetail::getStatus, 10));

		planService.updateStatusByOrderNumber(endCount,planCode);
	}

	/**
	 * 根据扫描去的物料
	 *
	 * @param scanCode
	 * @return
	 */
	public MaterialInfo findMaterialList(String scanCode) {
		List<MaterialInfo> materialList = OpcUaCache.getAllMat();
		if (materialList == null) {
			return null;
		}

		List<MaterialInfo> materialInfos = materialList.stream().filter(materialInfo -> scanCode.contains(materialInfo.getMaterialNo())).collect(Collectors.toList());
		if (materialInfos == null || materialInfos.size() == 0) {
			return null;
		}
		return materialInfos.get(0);
	}


	/**
	 * 查询最后一道工序
	 *
	 * @param matCode
	 * @return
	 */
	public PrdBomStation findLastStation(String matCode) {
		List<PrdBomStation> dataList = OpcUaCache.getBomStationByProd(matCode);
		if (dataList == null || dataList.size() == 0) {
			return null;
		}
		return dataList.get(dataList.size() - 1);
	}


	/**
	 * 根据位置查询符合条件配方
	 *
	 * @param site
	 * @param ctx
	 * @return
	 */
	public List<PrdBom> findBom(ProduceSite site, String groupNo, OpcUaVO ctx) {
		Set<String> prodList = findProdOnFirstStation(site);
		List<PrdBom> bomList = new ArrayList<>();
		if (prodList == null || prodList.size() == 0) {
			return bomList;
		}
		PrdBom bom;
		PrdInfo prd;
		for (String prod : prodList) {
			bom = OpcUaCache.getBomByMatCode(prod);
			if (bom == null) {
				continue;
			}
			if (!bom.getPosition().equals(ctx.getPosition())) {
				continue;
			}
			prd = OpcUaCache.getByProdCode(bom.getMaterialNo());
			if (prd == null) {
				continue;
			}
			//必须是同一组下的所有单据
			if (groupNo != null && !groupNo.equals(prd.getMaterialGroupNo())) {
				continue;
			}
			bomList.add(bom);
		}
		return bomList;
	}

	/**
	 * 本工序是第一工序的所有产品编码
	 *
	 * @param site
	 * @return
	 */
	private Set<String> findProdOnFirstStation(ProduceSite site) {
		Set<String> prodList = new HashSet<>();
		Map<String, List<PrdBomStation>> bomStationMap = OpcUaCache.getAllBomStation();
		for (Map.Entry<String, List<PrdBomStation>> entry : bomStationMap.entrySet()) {
			if (entry.getValue().get(0).getStationCode().equals(site.getStationCode())) {
				prodList.add(entry.getKey());
			}
		}
		return prodList;
	}

	/**
	 * 查询某个配方 指定工位所有物料
	 *
	 * @param stationCode
	 * @return
	 */
	public List<PrdBom> findBomMatListByStation(String prod, String stationCode) {
		List<PrdBom> dataList = new ArrayList<>();
		List<PrdBom> bomList = OpcUaCache.getBomMatByProd(prod);
		if (bomList != null) {
			bomList.forEach(b -> {
				if (stationCode.equals(b.getStationNo())) {
					dataList.add(b);
				}
			});
		}
		return dataList;
	}

	/**
	 * 查询某个配方 指定工位的指定物料
	 *
	 * @param stationCode
	 * @return
	 */
	public PrdBom findBomMatByStation(String prod, String mat, String stationCode) {
		List<PrdBom> dataList = new ArrayList<>();
		List<PrdBom> bomList = findBomMatListByStation(prod, stationCode);
		if (bomList != null) {
			bomList.forEach(b -> {
				if (b.getMaterialNo().equals(mat)) {
					dataList.add(b);
				}
			});
		}
		if (dataList == null || dataList.size() == 0) {
			return null;
		}
		return dataList.get(0);
	}

	/**
	 * 查询某个配方 指定工位的指定参数
	 *
	 * @param site
	 * @return
	 */
	public List<PrdBomParam> findBomParaByStation(String prod, ProduceSite site) {
		List<PrdBomParam> dataList = new ArrayList<>();
		List<PrdBomParam> bomList = OpcUaCache.getBomParamByProd(prod);
		if (bomList != null) {
			bomList.forEach(b -> {
				if (b.getStationCode().equals(site.getStationCode())) {
					dataList.add(b);
				}
			});
		}
		if (dataList == null || dataList.size() == 0) {
			return null;
		}
		return dataList;
	}


	/**
	 * 批次物料现有数量
	 *
	 * @param assembly
	 * @return
	 */
	private Map<String, StationBatch> getBatchCountMap(StationAssembly assembly) {
		//查询出所有的批次物料现有值
		LambdaQueryWrapper<StationBatch> lambdaQueryWrapper = new QueryWrapper<StationBatch>().lambda()
			.eq(StationBatch::getSiteCode, assembly.getSiteCode())
			.eq(StationBatch::getStationCode, assembly.getStationCode());
		List<StationBatch> batchList = stationBatchService.list(lambdaQueryWrapper);
		Map<String, StationBatch> batchDataMap = new HashMap<>();
		for (StationBatch t : batchList) {
			batchDataMap.put(t.getMaterialNo(), t);
		}
		return batchDataMap;
	}

	/**
	 * 批次物料扣减
	 * 必须先扣减，要不会两个同时上架订单征用统一批问题
	 * 如果业务上要求扫码产品码，恢复最初状态，那么批次物料的不能删除，因为已经扣减了
	 *
	 * @param assembly
	 * @param batchBomList 批次物料现有数量
	 * @param matUseMap    物料实际使用数量
	 * @param scanMap      已扫描信息
	 * @return
	 * @throws Exception
	 */
	private void batchMatUse(StationAssembly assembly, LMSDetail order, List<PrdBom> batchBomList, Map<String, Long> matUseMap, Map<String, ScanTemp> scanMap) throws Exception {
		//所有需要的物料
		if (batchBomList == null || batchBomList.size() == 0) {
			return;
		}
		Map<String, StationBatch> batchDataMap = null;
		MaterialInfo m;
		Long batchCount;
		ScanTemp st;
		for (PrdBom mat : batchBomList) {
			m = OpcUaCache.getByMatCode(mat.getMaterialNo());
			if (m == null) {
				continue;
			}
			//批扫物料
			if (!ScanTypeEnum.BATCH_SCAN.eq(m.getScanType())) {
				continue;
			}
			st = scanMap.get(m.getMaterialNo());
			//已经扣减过了
			if (matUseMap.containsKey(m.getMaterialNo())) {
				st.setActNum(st.getPlanNum());
				continue;
			}
			if (batchDataMap == null) {
				batchDataMap = getBatchCountMap(assembly);
			}
			StationBatch stationBatch = batchDataMap.get(m.getMaterialNo());
			if (stationBatch == null) {
				continue;
			}
			batchCount = stationBatch.getNum();
			if (batchCount == null) {
				batchCount = 0L;
			}
			//需要数量大于现在有的数量
			if (Long.valueOf(mat.getQuota()) > batchCount) {
				continue;
			}
			//满足条件，则扣减
			//获取当前工位批量物料信息,更新批追表数量
			UpdateWrapper<StationBatch> wrapper = new UpdateWrapper<>();
			wrapper.eq("id", stationBatch.getId());
			wrapper.set("num", batchCount - Long.parseLong(mat.getQuota()));
			wrapper.set("update_time", new Date());
			stationBatchService.update(wrapper);

			//插入一条批追记录
			AnalysisMaterialCompositionMysql analysisMaterialComposition = new AnalysisMaterialCompositionMysql();
			analysisMaterialComposition.setAssemblyBarcode(assembly.getAssemblyBarcode());
			analysisMaterialComposition.setAssemblyNo(assembly.getAssemblyNo());
			analysisMaterialComposition.setStation(assembly.getStationCode());
			analysisMaterialComposition.setSite(assembly.getSiteCode());
			analysisMaterialComposition.setLoadTime(new Date());
			analysisMaterialComposition.setPartNo(m.getMaterialNo());
			analysisMaterialComposition.setPartName(m.getMaterialName());
			analysisMaterialComposition.setScanCode(stationBatch.getBatchBarcode());
			analysisMaterialComposition.setBatchTraceCode(stationBatch.getBatchBarcode());
			analysisMaterialComposition.setSupplierBatch(order.getBatch());
			//精追码不赋值
//			analysisMaterialComposition.setChaserCode(stationBatch.getBatchBarcode());

			analysisMaterialComposition.setQuantity(Integer.valueOf(mat.getQuota()));
			analysisMaterialComposition.setPartType(ScanTypeEnum.BATCH_SCAN.getIdx());
			analysisMaterialCompositionService.save(analysisMaterialComposition);

			//该物料扣减成功
			st.setActNum(st.getPlanNum());
		}
	}


	/**
	 * 判断是否扫描完成
	 *
	 * @param ctx
	 */
	public void scanComplete(StationAssembly assembly, LMSDetail order, OpcUaVO ctx) throws Exception {
		//校验当前工位是否有物料
		List<PrdBom> matList = findBomMatListByStation(assembly.getAssemblyNo(), assembly.getStationCode());
		if (ObjectUtil.isEmpty(matList)) {
			OpcUa.getInstance().writeValueShortFinal(ctx, assembly.getPosition(), OpcUaKey.scanComplete, (short) 10);
			return;
		}
		Map<String, ScanTemp> scanMap = new HashMap<>();
		List<PrdBom> batchList = new ArrayList<>();
		List<PrdBom> oneList = new ArrayList<>();
		matList.forEach(mat -> {
			Long quota = 0L;
			try {
				quota = Long.valueOf(mat.getQuota());
			} catch (Exception ex) {
			}
			//初始化扫描信息
			scanMap.put(mat.getMaterialNo(), new ScanTemp(mat, quota, 0L));
			//过滤批次物料和其他物料
			MaterialInfo m = OpcUaCache.getByMatCode(mat.getMaterialNo());
			if (!ScanTypeEnum.BATCH_SCAN.eq(m.getScanType())) {
				oneList.add(mat);
			} else {
				batchList.add(mat);
			}
		});
		//检查所有物料的扫码完成情况
		List<MaterialCountVO> countList = analysisMaterialCompositionService.matCountByBarCode(assembly.getAssemblyBarcode(), assembly.getStationCode(), assembly.getSiteCode());
		Map<String, Long> matUseMap = CountMapUtil.toMap(countList);

		//扣减批次物料成功
		batchMatUse(assembly, order, batchList, matUseMap, scanMap);

		//其他实际数量
		oneList.forEach(prdBom -> {
			String matNo = prdBom.getMaterialNo();
			scanMap.get(matNo).setActNum(CountMapUtil.getValue(matUseMap, matNo));
		});

		boolean success = true;
		String key;
		for (ScanTemp temp : scanMap.values()) {
			key = temp.getMat().getPlcPosition();
			if (!temp.complete()) {
				success = false;
				OpcUa.getInstance().writeValueShortFinal(ctx, assembly.getPosition(), key, (short) 0);
			} else {
				OpcUa.getInstance().writeValueShortFinal(ctx, assembly.getPosition(), key, (short) 1);
			}
		}
		if (success) {
			OpcUa.getInstance().writeValueShortFinal(ctx, assembly.getPosition(), OpcUaKey.scanComplete, (short) 10);
		} else {
			OpcUa.getInstance().writeValueShortFinal(ctx, assembly.getPosition(), OpcUaKey.scanComplete, (short) 2);
		}

	}


	/**
	 * 校验当前工位的上序工位 是否归档
	 *
	 * @param barcode 总成流水号
	 * @return boolean
	 */
	public boolean validPreStation(String barcode, String prodCode, ProduceSite site) {
		//所有工序
		List<PrdBomStation> produceStations = OpcUaCache.getBomStationByProd(prodCode);
		//获取上序工位信息
		PrdBomStation preStation = null;
		for (int i = 0; i < produceStations.size(); i++) {
			if (produceStations.get(i).getStationCode().equals(site.getStationCode())) {
				if (i < produceStations.size() - 2) {
					preStation = produceStations.get(i + 1);
				}
			}
		}
		if (ObjectUtil.isEmpty(preStation)) {
			return true;
		}
		//查询前一道工序是否完成
		StationAssembly stationAssembly = stationAssemblyService.getOne(new QueryWrapper<StationAssembly>().lambda()
			.eq(StationAssembly::getStationCode, preStation.getStationCode())
			.eq(StationAssembly::getAssemblyBarcode, barcode)
			.last("limit 1"));
		return ObjectUtil.isNotEmpty(stationAssembly) && 1 == stationAssembly.getStatus();
	}


}
